//
// Copyright (C) OpenSim Ltd.
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public License
// as published by the Free Software Foundation; either version 2
// of the License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with this program; if not, see <http://www.gnu.org/licenses/>.
//
#include <algorithm>    

#include "inet/transportlayer/tcp/Tcp.h"
#include "inet/transportlayer/tcp/flavours/TcpReno.h"

namespace inet {
namespace tcp {

Register_Class(TcpReno);

TcpReno::TcpReno() : TcpTahoeRenoFamily(),
    state((TcpRenoStateVariables *&)TcpAlgorithm::state)
{
}

void TcpReno::recalculateSlowStartThreshold()
{
    // When a TCP sender detects segment loss using the retransmission
    // timer, the value of ssthresh MUST be set to no more than the value
    // given in :
    //
    //   ssthresh = max (FlightSize / 2, 2*SMSS)            
    //
    // FlightSize is the amount of outstanding data in
    // the network."

    // set ssthresh to flight size / 2, but at least 2 SMSS

    uint32 flight_size = std::min(state->snd_cwnd, state->snd_wnd);    

    state->ssthresh = std::max(flight_size / 2, 2 * state->snd_mss);

    conn->emit(ssthreshSignal, state->ssthresh);
}

void TcpReno::processRexmitTimer(TcpEventCode& event)
{
    TcpTahoeRenoFamily::processRexmitTimer(event);

    if (event == TCP_E_ABORT)
        return;

    // After REXMIT timeout TCP Reno should start slow start with snd_cwnd = snd_mss.
    //
    // If calling "retransmitData();" there is no rexmit limitation (bytesToSend > snd_cwnd)
    // therefore "sendData();" has been modified and is called to rexmit outstanding data.
    //
    // 
    // Furthermore, upon a timeout cwnd MUST be set to no more than the loss
    // window, LW, which equals 1 full-sized segment (regardless of the
    // value of IW).  Therefore, after retransmitting the dropped segment
    // the TCP sender uses the slow start algorithm to increase the window
    // from 1 full-sized segment to the new value of ssthresh, at which
    // point congestion avoidance again takes over.

    // begin Slow Start 
    recalculateSlowStartThreshold();
    state->snd_cwnd = state->snd_mss;

    conn->emit(cwndSignal, state->snd_cwnd);

    EV_INFO << "Begin Slow Start: resetting cwnd to " << state->snd_cwnd
            << ", ssthresh=" << state->ssthresh << "\n";

    state->afterRto = true;

    conn->retransmitOneSegment(true);
}

void TcpReno::receivedDataAck(uint32 firstSeqAcked)
{
    TcpTahoeRenoFamily::receivedDataAck(firstSeqAcked);

    if (state->dupacks >= DUPTHRESH) {    // DUPTHRESH = 3
        //
        // Perform Fast Recovery: set cwnd to ssthresh (deflating the window).
        //
        EV_INFO << "Fast Recovery: setting cwnd to ssthresh=" << state->ssthresh << "\n";
        state->snd_cwnd = state->ssthresh;

        conn->emit(cwndSignal, state->snd_cwnd);
    }
    else {
        bool performSsCa = true; //Stands for: "perform slow start and congestion avoidance"
        if (state && state->ect && state->gotEce) {
            // halve cwnd and reduce ssthresh and do not increase cwnd:
            //   If the sender receives an ECN-Echo (ECE) ACK
            // packet, then the sender knows that congestion was encountered in the
            // network on the path from the sender to the receiver.  The indication
            // of congestion should be treated just as a congestion loss in non-
            // ECN-Capable TCP. That is, the TCP source halves the congestion window
            // "cwnd" and reduces the slow start threshold "ssthresh".  The sending
            // TCP SHOULD NOT increase the congestion window in response to the
            // receipt of an ECN-Echo ACK packet.
            // ...
            //   The value of the congestion window is bounded below by a value of one MSS.
            // ...
            //   TCP should not react to congestion indications more than once every
            // window of data (or more loosely, more than once every round-trip
            // time). That is, the TCP sender's congestion window should be reduced
            // only once in response to a series of dropped and/or CE packets from a
            // single window of data.  In addition, the TCP source should not decrease
            // the slow-start threshold, ssthresh, if it has been decreased
            // within the last round trip time.
            if (simTime() - state->eceReactionTime > state->srtt) {
                state->ssthresh = state->snd_cwnd / 2;
                state->snd_cwnd = std::max(state->snd_cwnd / 2, uint32(1));
                state->sndCwr = true;
                performSsCa = false;
                EV_INFO
                               << "ssthresh = cwnd/2: received ECN-Echo ACK... new ssthresh = "
                               << state->ssthresh << "\n";
                EV_INFO << "cwnd /= 2: received ECN-Echo ACK... new cwnd = "
                               << state->snd_cwnd << "\n";

               
                // The sending TCP MUST reset the retransmit timer on receiving
                // the ECN-Echo packet when the congestion window is one.
                if (state->snd_cwnd == 1) {
                    restartRexmitTimer();
                    EV_INFO << "cwnd = 1... reset retransmit timer.\n";
                }
                state->eceReactionTime = simTime();
                conn->emit(cwndSignal, state->snd_cwnd);
                conn->emit(ssthreshSignal, state->ssthresh);
            }
            else
                EV_INFO << "multiple ECN-Echo ACKs in less than rtt... no ECN reaction\n";
            state->gotEce = false;
        }
        if (performSsCa) {
            // If ECN is not enabled or if ECN is enabled and received multiple ECE-Acks in
            // less than RTT, then perform slow start and congestion avoidance.

            if (state->snd_cwnd < state->ssthresh) {
                EV_INFO << "cwnd <= ssthresh: Slow Start: increasing cwnd by one SMSS bytes to ";

                // perform Slow Start
                state->snd_cwnd += state->snd_mss;

                conn->emit(cwndSignal, state->snd_cwnd);
                conn->emit(ssthreshSignal, state->ssthresh);

                EV_INFO << "cwnd=" << state->snd_cwnd << "\n";
            }
            else {
                // perform Congestion Avoidance 
                uint32 incr = state->snd_mss * state->snd_mss / state->snd_cwnd;

                if (incr == 0)
                    incr = 1;

                state->snd_cwnd += incr;

                conn->emit(cwndSignal, state->snd_cwnd);
                conn->emit(ssthreshSignal, state->ssthresh);

                EV_INFO << "cwnd > ssthresh: Congestion Avoidance: increasing cwnd linearly, to " << state->snd_cwnd << "\n";
            }
        }
    }

    if (state->sack_enabled && state->lossRecovery) {
        // Once a TCP is in the loss recovery phase the following procedure MUST
        // be used for each arriving ACK:
        //
        // (A) An incoming cumulative ACK for a sequence number greater than
        // RecoveryPoint signals the end of loss recovery and the loss
        // recovery phase MUST be terminated.  Any information contained in
        // the scoreboard for sequence numbers greater than the new value of
        // HighACK SHOULD NOT be cleared when leaving the loss recovery
        // phase."
        if (seqGE(state->snd_una, state->recoveryPoint)) {
            EV_INFO << "Loss Recovery terminated.\n";
            state->lossRecovery = false;
        }
        // (B) Upon receipt of an ACK that does not cover RecoveryPoint the
        //following actions MUST be taken:
        //
        // (B.1) Use Update () to record the new SACK information conveyed by the incoming ACK.
        //
        // (B.2) Use SetPipe () to re-calculate the number of octets still in the network
        else {
            // update of scoreboard (B.1) has already be done in readHeaderOptions()
            conn->setPipe();

            // (C) If cwnd - pipe >= 1 SMSS the sender SHOULD transmit one or more segments as follows:"
            if (((int)state->snd_cwnd - (int)state->pipe) >= (int)state->snd_mss) // Note: Typecast needed to avoid prohibited transmissions
                conn->sendDataDuringLossRecoveryPhase(state->snd_cwnd);
        }
    }

    sendData(false);
}

void TcpReno::receivedDuplicateAck()
{
    TcpTahoeRenoFamily::receivedDuplicateAck();

    if (state->dupacks == DUPTHRESH) {    // DUPTHRESH = 3
        EV_INFO << "Reno on dupAcks == DUPTHRESH(=3): perform Fast Retransmit, and enter Fast Recovery:";

        if (state->sack_enabled) {
            // When a TCP sender receives the duplicate ACK corresponding to
            // DupThresh ACKs, the scoreboard MUST be updated with the new SACK
            // information.  If no previous loss event has occurred
            // on the connection or the cumulative acknowledgment point is beyond
            // the last value of RecoveryPoint, a loss recovery phase SHOULD be
            // initiated
            // The following steps MUST be taken:
            //
            // (1) RecoveryPoint = HighData
            //
            // When the TCP sender receives a cumulative ACK for this data octet
            // the loss recovery phase is terminated."

            // If an RTO occurs during loss recovery as specified in this document,
            // RecoveryPoint MUST be set to HighData.  Further, the new value of
            // RecoveryPoint MUST be preserved and the loss recovery algorithm
            // outlined in this document MUST be terminated.  In addition, a new
            // recovery phase MUST NOT be initiated
            // until HighACK is greater than or equal to the new value of
            // RecoveryPoint."
            if (state->recoveryPoint == 0 || seqGE(state->snd_una, state->recoveryPoint)) {    // HighACK = snd_una
                state->recoveryPoint = state->snd_max;    // HighData = snd_max
                state->lossRecovery = true;
                EV_DETAIL << " recoveryPoint=" << state->recoveryPoint;
            }
        }
        // After the fast retransmit algorithm sends what appears to be the
        // missing segment, the "fast recovery" algorithm governs the
        // transmission of new data until a non-duplicate ACK arrives.
        // the TCP sender can continue to transmit new segments 

        // enter Fast Recovery
        recalculateSlowStartThreshold();
        // "set cwnd to ssthresh plus 3 * SMSS." 
        state->snd_cwnd = state->ssthresh + 3 * state->snd_mss;    

        conn->emit(cwndSignal, state->snd_cwnd);

        EV_DETAIL << " set cwnd=" << state->snd_cwnd << ", ssthresh=" << state->ssthresh << "\n";

        // Fast Retransmission: retransmit missing segment without waiting
        // for the REXMIT timer to expire
        conn->retransmitOneSegment(false);

        if (state->sack_enabled) {
            // Run SetPipe ()
            //
            // Set a "pipe" variable  to the number of outstanding octets
            // currently "in the pipe"; this is the data which has been sent by
            // the TCP sender but for which no cumulative or selective
            // acknowledgment has been received and the data has not been
            // determined to have been dropped in the network.  It is assumed
            // that the data is still traversing the network path."
            conn->setPipe();
            //  In order to take advantage of potential additional available
            // cwnd, proceed to step below."
            if (state->lossRecovery) {
                // Therefore we give implementers the latitude to use the standard
                // style RTO management or, optionally, a more careful variant
                // that re-arms the RTO timer on each retransmission that is sent during
                // recovery MAY be used.  This provides a more conservative timer than
                // specified in , and so may not always be an attractive
                // alternative.  However, in some cases it may prevent needless
                // retransmissions, go-back-N transmission and further reduction of the
                // congestion window."

                EV_INFO << "Retransmission sent during recovery, restarting REXMIT timer.\n";
                restartRexmitTimer();

                //  If cwnd - pipe >= 1 SMSS the sender SHOULD transmit one or more segments as follows
                if (((int)state->snd_cwnd - (int)state->pipe) >= (int)state->snd_mss) // Note: Typecast needed to avoid prohibited transmissions
                    conn->sendDataDuringLossRecoveryPhase(state->snd_cwnd);
            }
        }

        // try to transmit new segments
        sendData(false);
    }
    else if (state->dupacks > DUPTHRESH) {    // DUPTHRESH = 3
        //
        // Reno: For each additional duplicate ACK received, increment cwnd by SMSS.
        // This artificially inflates the congestion window in order to reflect the
        // additional segment that has left the network
        //
        state->snd_cwnd += state->snd_mss;
        EV_DETAIL << "Reno on dupAcks > DUPTHRESH(=3): Fast Recovery: inflating cwnd by SMSS, new cwnd=" << state->snd_cwnd << "\n";

        conn->emit(cwndSignal, state->snd_cwnd);

        // Retransmission Timeouts
        sendData(false);
    }
}

} // namespace tcp
} // namespace inet

